use crate::{
    BufferId, BufferSnapshot, Point, PointUtf16, TextDimension, ToOffset, ToPoint, ToPointUtf16,
    locator::Locator,
};
use std::{cmp::Ordering, fmt::Debug, ops::Range};
use sum_tree::{Bias, Dimensions};

/// A timestamped position in a buffer
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Anchor {
    /// The timestamp of the operation that inserted the text
    /// in which this anchor is located.
    pub timestamp: clock::Lamport,
    /// The byte offset into the text inserted in the operation
    /// at `timestamp`.
    pub offset: usize,
    /// Whether this anchor stays attached to the character *before* or *after*
    /// the offset.
    pub bias: Bias,
    pub buffer_id: Option<BufferId>,
}

impl Debug for Anchor {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.is_min() {
            return write!(f, "Anchor::min({:?})", self.buffer_id);
        }
        if self.is_max() {
            return write!(f, "Anchor::max({:?})", self.buffer_id);
        }

        f.debug_struct("Anchor")
            .field("timestamp", &self.timestamp)
            .field("offset", &self.offset)
            .field("bias", &self.bias)
            .field("buffer_id", &self.buffer_id)
            .finish()
    }
}

impl Anchor {
    pub const MIN: Self = Self {
        timestamp: clock::Lamport::MIN,
        offset: usize::MIN,
        bias: Bias::Left,
        buffer_id: None,
    };

    pub const MAX: Self = Self {
        timestamp: clock::Lamport::MAX,
        offset: usize::MAX,
        bias: Bias::Right,
        buffer_id: None,
    };

    pub fn min_for_buffer(buffer_id: BufferId) -> Self {
        Self {
            timestamp: clock::Lamport::MIN,
            offset: usize::MIN,
            bias: Bias::Left,
            buffer_id: Some(buffer_id),
        }
    }

    pub fn max_for_buffer(buffer_id: BufferId) -> Self {
        Self {
            timestamp: clock::Lamport::MAX,
            offset: usize::MAX,
            bias: Bias::Right,
            buffer_id: Some(buffer_id),
        }
    }

    pub fn min_min_range_for_buffer(buffer_id: BufferId) -> std::ops::Range<Self> {
        let min = Self::min_for_buffer(buffer_id);
        min..min
    }
    pub fn max_max_range_for_buffer(buffer_id: BufferId) -> std::ops::Range<Self> {
        let max = Self::max_for_buffer(buffer_id);
        max..max
    }
    pub fn min_max_range_for_buffer(buffer_id: BufferId) -> std::ops::Range<Self> {
        Self::min_for_buffer(buffer_id)..Self::max_for_buffer(buffer_id)
    }

    pub fn cmp(&self, other: &Anchor, buffer: &BufferSnapshot) -> Ordering {
        let fragment_id_comparison = if self.timestamp == other.timestamp {
            Ordering::Equal
        } else {
            buffer
                .fragment_id_for_anchor(self)
                .cmp(buffer.fragment_id_for_anchor(other))
        };

        fragment_id_comparison
            .then_with(|| self.offset.cmp(&other.offset))
            .then_with(|| self.bias.cmp(&other.bias))
    }

    pub fn min<'a>(&'a self, other: &'a Self, buffer: &BufferSnapshot) -> &'a Self {
        if self.cmp(other, buffer).is_le() {
            self
        } else {
            other
        }
    }

    pub fn max<'a>(&'a self, other: &'a Self, buffer: &BufferSnapshot) -> &'a Self {
        if self.cmp(other, buffer).is_ge() {
            self
        } else {
            other
        }
    }

    pub fn bias(&self, bias: Bias, buffer: &BufferSnapshot) -> Anchor {
        match bias {
            Bias::Left => self.bias_left(buffer),
            Bias::Right => self.bias_right(buffer),
        }
    }

    pub fn bias_left(&self, buffer: &BufferSnapshot) -> Anchor {
        match self.bias {
            Bias::Left => *self,
            Bias::Right => buffer.anchor_before(self),
        }
    }

    pub fn bias_right(&self, buffer: &BufferSnapshot) -> Anchor {
        match self.bias {
            Bias::Left => buffer.anchor_after(self),
            Bias::Right => *self,
        }
    }

    pub fn summary<D>(&self, content: &BufferSnapshot) -> D
    where
        D: TextDimension,
    {
        content.summary_for_anchor(self)
    }

    /// Returns true when the [`Anchor`] is located inside a visible fragment.
    pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool {
        if self.is_min() || self.is_max() {
            true
        } else if self.buffer_id.is_none_or(|id| id != buffer.remote_id) {
            false
        } else {
            let Some(fragment_id) = buffer.try_fragment_id_for_anchor(self) else {
                return false;
            };
            let (.., item) = buffer
                .fragments
                .find::<Dimensions<Option<&Locator>, usize>, _>(
                    &None,
                    &Some(fragment_id),
                    Bias::Left,
                );
            item.is_some_and(|fragment| fragment.visible)
        }
    }

    pub fn is_min(&self) -> bool {
        self.timestamp == clock::Lamport::MIN
            && self.offset == usize::MIN
            && self.bias == Bias::Left
    }

    pub fn is_max(&self) -> bool {
        self.timestamp == clock::Lamport::MAX
            && self.offset == usize::MAX
            && self.bias == Bias::Right
    }
}

pub trait OffsetRangeExt {
    fn to_offset(&self, snapshot: &BufferSnapshot) -> Range<usize>;
    fn to_point(&self, snapshot: &BufferSnapshot) -> Range<Point>;
    fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> Range<PointUtf16>;
}

impl<T> OffsetRangeExt for Range<T>
where
    T: ToOffset,
{
    fn to_offset(&self, snapshot: &BufferSnapshot) -> Range<usize> {
        self.start.to_offset(snapshot)..self.end.to_offset(snapshot)
    }

    fn to_point(&self, snapshot: &BufferSnapshot) -> Range<Point> {
        self.start.to_offset(snapshot).to_point(snapshot)
            ..self.end.to_offset(snapshot).to_point(snapshot)
    }

    fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> Range<PointUtf16> {
        self.start.to_offset(snapshot).to_point_utf16(snapshot)
            ..self.end.to_offset(snapshot).to_point_utf16(snapshot)
    }
}

pub trait AnchorRangeExt {
    fn cmp(&self, b: &Range<Anchor>, buffer: &BufferSnapshot) -> Ordering;
    fn overlaps(&self, b: &Range<Anchor>, buffer: &BufferSnapshot) -> bool;
}

impl AnchorRangeExt for Range<Anchor> {
    fn cmp(&self, other: &Range<Anchor>, buffer: &BufferSnapshot) -> Ordering {
        match self.start.cmp(&other.start, buffer) {
            Ordering::Equal => other.end.cmp(&self.end, buffer),
            ord => ord,
        }
    }

    fn overlaps(&self, other: &Range<Anchor>, buffer: &BufferSnapshot) -> bool {
        self.start.cmp(&other.end, buffer).is_lt() && other.start.cmp(&self.end, buffer).is_lt()
    }
}
